/**
 * @author rdru
 * INV: 
 */
package server;
	import static server.ServerMonitor.docAdd;
	import static server.ServerMonitor.docClose;
	import static server.ServerMonitor.docOpen;
	import static server.ServerMonitor.docRemove;
	import static server.ServerMonitor.lockGrant;
	import static server.ServerMonitor.lockRelease;
	import static server.ServerMonitor.lockRequest;
	import static server.ServerMonitor.logDocClose;
	import static server.ServerMonitor.logDocOpen;
	import static server.ServerMonitor.logLockDeny;
	import static server.ServerMonitor.logLockGrant;
	import static server.ServerMonitor.logLockRelease;
	import static server.ServerMonitor.logLockRequest;
	import static server.ServerMonitor.logRecieved;
	import static server.ServerMonitor.logUserLogin;
	import static server.ServerMonitor.logUserLogout;
	import static server.ServerMonitor.received;
	import static server.ServerMonitor.serverLogAdd;
	import static server.ServerMonitor.userLogin;
	import static server.ServerMonitor.userLogout;
	
	import java.io.BufferedReader;
	import java.io.BufferedWriter;
	import java.io.DataInputStream;
	import java.io.DataOutputStream;
	import java.io.FileInputStream;
	import java.io.FileOutputStream;
	import java.io.InputStreamReader;
	import java.io.OutputStreamWriter;
	import java.util.HashMap;
	import java.util.LinkedList;
	import java.util.SortedMap;
	import java.util.SortedSet;
	import java.util.TreeSet;
	
	import javax.swing.JOptionPane;
	
	import abstractions.LoggedUser;
	import abstractions.AtomId;
	import abstractions.CommandC;
	import abstractions.Doc;
	import abstractions.OpenDoc;
	import abstractions.User;
	import server.ServerMonitor;

public class Server {
	// *************** Server windows
	static ServerW			serverW;
	static ServerSetup		setup;
	static ServerMonitor	monitor;
	
	// Names/URL of knwn Servers: this should eventualy be loaded from a file
	private final String[]		serverNames		= {"Local"};
	private final String[]		serverURL		= {"http://localhost"};
	private final static String	serverPath		= "/Users/Shared/EMU_Server/";	
	private final String		EMUUserFile		= serverPath + "userList";
	private final String		sharedDirectory	= serverPath + "shared";
	private final String		admin			= "admin";

	// user and files known to the Server
	static HashMap<String, Doc>		regDocs;		// <docPath, doc>
	static HashMap<String, User>		regUsers;		// <logname, User>
	static TreeSet<String>				regUsersName;	// <logname>
	static HashMap<String, String>		passwords;		// passwords of reg users <logname, password>

	// dynamic data (currently logged clients and open documents)
	static HashMap<Integer, ActiveDoc>			activeDocs;				// docId, Doc
	public static HashMap<String, Integer>		openDocs;				// docName, docId
	static HashMap<Integer, LoggedUser>		loggedUsers;			// userId, User
	static HashMap<Integer, String>			clients;				// clientId, IP
	
	// id manager (for now the ids are generated sequentially...)
	int								myServerId		= 0;
	public static int				nextUserId		= 0;
	public static int				nextClientId	= 0;
	public static int				nextDocId		= 0;
	
	// server monitor log
	public static StringBuilder	fullLog			= new StringBuilder();
	
	Server() {
		String password;

		// create main structures
		regDocs = new HashMap<String, Doc>();
		regUsers = new HashMap<String, User>();
		regUsersName = new TreeSet<String>();
		passwords = new HashMap<String, String>();
		activeDocs = new HashMap<Integer, ActiveDoc>();
		openDocs = new HashMap<String, Integer>();
		loggedUsers = new HashMap<Integer, LoggedUser>();
		clients = new HashMap<Integer, String>();
		
		// read the password file
		try{
			FileInputStream fstream = new FileInputStream(serverPath + "password");
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String line;
			while ((line = br.readLine()) != null)   {
				String[] s = line.split("\t");
				System.out.println (s[0] + " password: " + s[1]);
				passwords.put(s[0], s[1]);
				if (!passwords.get(s[0]).equals(s[1]))
					System.out.println("error passwords");
			}
			in.close();
		}catch (Exception e){
			System.err.println("Error: " + e.getMessage());
		}
		
		do {
			// get adm password... and validate it
			password = new String();		// substitute for user input password
			if (!passwords.get(admin).equals(password)) {
				// show invalid authentication dialog
				System.out.println("wrong admin password");
			}
		} while (false);
//		} while (!passwords.get(admin).equals(password));
		
		// init the Server windows
		serverW	= new ServerW();
		setup	= new ServerSetup();
		monitor = new ServerMonitor();
		
		
		// reads the file list from file system and inserts than into docsList ...
		// at boot, the server adds all files from the shared file directory.

		
		// start all service threads
		
		// show server window
		serverW.setVisible(true);
		
	}
	
	
	// *************** local services
	
	// writes password file
	static void writePassword() {
		try{
			FileOutputStream fstream = new FileOutputStream(serverPath + "password");
			DataOutputStream out = new DataOutputStream(fstream);
			BufferedWriter br = new BufferedWriter(new OutputStreamWriter(out));
			String line;
			for (String name: regUsersName) {
				// write name
				br.write(name + "\t" + passwords.get(name) + "\n");
				System.out.println(name + "\t" + passwords.get(name));
			}
			out.close();
		}catch (Exception e){
			System.err.println("Error: " + e.getMessage());
		}
	}

	// 
	// returns true if pair <Client/User> is valid and on the loggedUsers list,
	// false otherwise.	
	static LoggedUser validateClient(int clientId, int userId) {
		LoggedUser u;
		if (! validateClient(clientId)
		    || (u = loggedUsers.get(userId)) == null)
			return null;
		return u;
	}

	static boolean validateClient(int clientId) {
		return clients.containsKey(clientId);
	}

	// returns true if user is validated, false o.w.
	// Pre: logname is not null
	static boolean validateUser(String logname, String password) {
		return password.equals(passwords.get(logname));
	}

	static ActiveDoc openFile(Doc d) {
		// open the doc file and read its contains into text...
		String text = "substitute by the doc text";
		
		if (false)			// substitute false by expression: can not open/read file
			return null;
		ActiveDoc ad = new ActiveDoc(d, text);		
		return ad;
	}

	static boolean saveCloseFile(int docId) {
		return true;
	}
	
	// addDoc: add a new document to regDocs.
	// returns true in case of success and false otherwise
	static boolean docAdd(int clientId, int userId, String docName, String docText) {
		//logDocAdd(userId, docName);
		if (validateClient(clientId, userId) == null) {
			serverLogAdd(docAdd.isSelected(), ": Failed validate clientId");
			return false;
		}
		// add doc to regDocs and create a new file for the new doc...
		return true;
	}

	static boolean docRemove(int clientId, int userId, String docName) {
		//logDocRemove(userId, docName);
		if (validateClient(clientId, userId) == null) {
			serverLogAdd(docRemove.isSelected(), " Failed validate clientId or userId!!");
			return false;
		}

		// do not know how to handle this yet ...
		return true;
	}

	static int registerClient(String IP) {
		clients.put(nextClientId++, IP);
		return nextClientId - 1;
	}

	static void removeClient(int clientId) {
		validateClient(clientId);
		clients.remove(clientId);
	}

	static void readPasswd() {
		
	}

	static void writePasswd() {
		
	}
	
	// adds a new user in the server regUsers list.
	static public void addUser(String logname, String password, String userName, 
		String userEmail) {
		if (regUsers.containsKey(logname)) {
			JOptionPane.showMessageDialog(monitor.getContentPane(),
					"Add User:\nLogname already in use.", "Add User", 
					JOptionPane.ERROR_MESSAGE);
			return;
		}

		// inserts the user
		regUsers.put(logname, new User(logname, userEmail, userName));
		passwords.put(logname, password);
		regUsersName.add(logname);
		
		// save the password file (to prevent corruption on a power failure)...
		writePassword();
	}
	
	// remove a user from the user list.  Returns false on error, or true otherwise
	// Pre: user does not have an open session
	static public boolean removeUser(User u) {
		if (loggedUsers.containsKey(u)) {
            JOptionPane.showMessageDialog(monitor.getContentPane(),
                    "Remove User:\nUser is currently active, can not remove.", 
                    "Remove User", JOptionPane.ERROR_MESSAGE);
            return false;
		}
		regUsers.remove(u);
		passwords.remove(u.logname);
		regUsersName.remove(u.logname);
		
		// save the password file (to prevent corruption on a power failure)...
		writePassword();
		return true;
	}
	
	// 
	public static boolean validateAdm() {
		// validadte adm...
        JOptionPane.showMessageDialog(monitor.getContentPane(),
                "Can not validate Admin user", "Admin Authentication", 
                JOptionPane.ERROR_MESSAGE);
        return true;
	}
	
	// add user to the list of users editing a given doc
	public static void addUserToDoc(String client, int userId, String docName) {}
	
	// remove user from the list of users editing a given doc
	public void removeUserToDoc(String client, int userId, String docName) {}
	
	public static String userName(int userId) {
		String s = null;
		return s;
	}
	
	public static String docName(int docId) {
		String s = null;
		return s;
	}
	
	public static String[] userNames(int[] users) {
		String s[] = null;
		return s;
	}
	
	public static String[] docNames(int[] docs) {
		String[] s = null;
		return s;
	}
	
	
	// logs all 

	// **************** service provided to clients:
	
	// authenticate the given logname and register the user as loged on client c.
	// Returns the User data in case of success, otherwise returns null.
	public static LoggedUser login(int clientId, String logname, String password) {
		logUserLogin(clientId, logname);
		if (! validateClient(clientId))
			return null;
		if (! validateUser(logname, password)) {
			serverLogAdd(userLogin.isSelected(), " Failed wrong logname/pasword!!");
			return null;
		}
		LoggedUser u = (LoggedUser) regUsers.get(logname);
		if (loggedUsers.containsValue(u)) {
			serverLogAdd(userLogin.isSelected(), " Failed already logged!!");
			return null;
		}
		loggedUsers.put(nextUserId++, u);
		return u;
	}
	
	// logout the user: release all locks and send any pending updates to other clients,
	// removes all associated docs from the docsList, 
	// remove the user from the regUsers and send to all clients editing some
	// of this user's docs a removeUserLocally.
	static boolean logout(int clientId, int userId) {
		logUserLogout(userId);
		LoggedUser u;
		if ((u = validateClient(clientId, userId)) == null) {
			serverLogAdd(userLogout.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		if (!u.edits.isEmpty()) {
			serverLogAdd(userLogout.isSelected(), " Failed needs to close open docs");
			return false;
		}
		loggedUsers.remove(userId);
		return true;
	}

		
	// remove a doc from the server managed documents docsList.
	public static boolean removeDocument(String client, int userId, String docName) {
		return true;
	}
	
	// openDoc: opens an existing document stored in the server.
	// For now the server handles all documents in a single directory and
	// all users have the same access rights...
	// docPath (for now) is just the file's name.
	// Returns the docId of the opened document, or -1 in case of error ...
	public static OpenDoc docOpen(int clientId, int userId, String docName, String docPath) {
		logDocOpen(userId, docName);
		LoggedUser u;
		if ((u = validateClient(clientId, userId)) == null) {
			serverLogAdd(docOpen.isSelected(), " Failed validate clientId/userId");
			return null;
		}
		
		// 
		Doc d = regDocs.get(docPath+docName);
		if (d == null) {
			serverLogAdd(docOpen.isSelected(), " Failed doc does not exist!!");
			return null;
		}
		 
		// if doc is not open, open it
		ActiveDoc ad;
		int docId = openDocs.get(docPath+docName);
		if (docId < 0) {
			ad = openFile(d);
			if (ad == null) {
				serverLogAdd(docOpen.isSelected(), " Failed can not open doc file");
				return null;
			}
		} else
			ad = activeDocs.get(docId); // opens and add to activeDocs
		
		// add user to editedBy
		ad.editedBy.add(userId);
		return ad;
	}

	public static void docClose(int clientId, int userId, int docId) {
		logDocClose(userId, docId);
		LoggedUser u;
		if ((u = validateClient(clientId, userId)) == null) {
			serverLogAdd(docClose.isSelected(), " Failed validate clientId/userId");
			return;
		}
		ActiveDoc d =  activeDocs.get(docId);
		d.upr = d.histOld.size() + d.histNew.size();	// updates received
		d.ups = d.histOld.size();						// updates sent
		d.n = 0;										// total # commands received
		for (LinkedList<CommandC> c: d.histOld)
			d.n += c.size();
		d.n += d.histNew.size();
		if ((d = activeDocs.get(docId)) == null) {
			serverLogAdd(docClose.isSelected(), " Failed doc does not exist!!");
			return;
		}
		d.editedBy.remove(docId);
		u.edits.remove(docId);
		if (d.editedBy.isEmpty()) {
			// update and save doc to file...
			activeDocs.remove(docId);
		}
		serverLogAdd(docClose.isSelected(), ", received: " + d.upr
		             + " sent: " + d.ups + " comands:" + d.n);
	}

	public static boolean lockRequest(int clientId, int userId, int docId, AtomId aId) {
		logLockRequest(userId, docId, aId);
		if (validateClient(clientId, userId) == null) {
			serverLogAdd(lockRequest.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		ActiveDoc d = activeDocs.get(docId);
		if (d.locked.containsKey(aId)) {
			logLockDeny(userId, docId, aId);
			return false;
		} else {
			logLockGrant(userId, docId, aId);
			d.locked.put(aId, userId);
			return true;
		}
	}

	public static boolean lockRelease(int clientId, int userId, int docId, AtomId aId) {
		logLockRelease(userId, docId, aId);
		if (validateClient(clientId, userId) == null) {
			serverLogAdd(lockRelease.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		ActiveDoc d = activeDocs.get(docId);
		int uId = d.locked.get(aId);
		if (uId == -1 || uId != userId) {
			serverLogAdd(lockGrant.isSelected(), 
				" Failed non-existent lock or locked to other user");
			return false;
		}
		d.locked.remove(aId);
		return true;
	}

	static boolean updateDoc(int clientId, int userId, int docId, LinkedList<CommandC> cmds) {
		logRecieved(userId, docId, cmds.size());
		LoggedUser u = validateClient(clientId, userId);
		if (u == null) {
			serverLogAdd(received.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		ActiveDoc d = u.edits.get(docId);
		if (d != null) {
			d.histNew.addAll(cmds);
			return true;
		} else {
			serverLogAdd(received.isSelected(), " Failed: user does not have this docId open");
			return false;
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//Schedule a job for the event-dispatching thread:
		//creating and showing this application's GUI.
		javax.swing.SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				// connect with the server

				// creates the thread pool for server edits
				
				// instatiate a new server
				new Server();
				
				// init the first EMU Window
				serverW.setVisible(true);
			}
		});
	}
}

